//
// Created by User on 24-10-7.
//
#include "general_encoder.h"
#include "video_config.hpp"
#include "frame_provider.hpp"
#include "audio_config.hpp"
#include "media_frame.h"

#include <glog/logging.h>


extern "C" {
#include <libavcodec/avcodec.h>
}

namespace encoder
{
    general_encoder::general_encoder(const utils::video_config& config, interfaces::frame_provider& provider)
        : m_provider(provider)
    {
        try
        {
            this->init_video_encoder(config);
        }
        catch (error::encoder_error& error)
        {
            this->clean_encoder(error.get_type());
            throw;
        }
        catch (...)
        {
            this->clean_encoder(error::encoder_error::type::UNKNOWN_ERROR);
            throw;
        }
    }

    general_encoder::general_encoder(const utils::audio_config& config, interfaces::frame_provider& provider)
        : m_provider(provider)
    {
        try
        {
            this->init_audio_encoder(config);
        }
        catch (error::encoder_error& error)
        {
            this->clean_encoder(error.get_type());
            throw;
        }
        catch (...)
        {
            this->clean_encoder(error::encoder_error::type::UNKNOWN_ERROR);
            throw;
        }
    }

    utils::media_packet general_encoder::get_packet()
    {
        if (this->is_end && this->m_que.empty())
        {
            return {};
        }
        if (!this->is_end && this->m_que.size() < 5)
        {
            this->encode_packet();
        }
        auto temp = this->m_que.front();
        this->m_que.pop();
        return temp;
    }

    const AVCodecParameters* general_encoder::get_code_parameters() const
    {
        if (!this->m_code_par)
        {
            this->m_code_par = avcodec_parameters_alloc();
            if (!this->m_code_par)
            {
                throw error::encoder_error(error::encoder_error::type::PARAMETERS_ALLOC_ERROR,
                                           "can not alloc parameter");
            }
            int ret = avcodec_parameters_from_context(this->m_code_par, this->m_code_ctx);
            if (ret < 0)
            {
                throw error::encoder_error(error::encoder_error::type::PARAMETERS_SET_ERROR, ret);
            }
        }
        return this->m_code_par;
    }

    AVRational general_encoder::get_timebase() const
    {
        return this->m_code_ctx->time_base;
    }

    int general_encoder::total_frame() const
    {
        return this->m_provider.total_frames();
    }

    general_encoder::~general_encoder()
    {
        this->clean_encoder(error::encoder_error::type::UNKNOWN_ERROR);
    }

    const AVCodec* general_encoder::get_video_encoder(const utils::video_config& config)
    {
        return avcodec_find_encoder(this->m_provider.get_codec()->codec_id);
    }

    const AVCodec* general_encoder::get_audio_encoder(const utils::audio_config& config)
    {
        return avcodec_find_encoder(this->m_provider.get_codec()->codec_id);
    }

    void general_encoder::init_video_encoder(const utils::video_config& config)
    {
        // 查找编码器
        this->m_codec = this->get_video_encoder(config);
        if (!this->m_codec)
        {
            throw error::encoder_error(error::encoder_error::type::ENCODER_NOT_FOUND, "can not found encoder");
        }
        LOG(INFO) << "find video encoder : " << this->m_codec->long_name;

        //分配上下文
        this->m_code_ctx = avcodec_alloc_context3(this->m_codec);
        if (!this->m_code_ctx)
        {
            throw error::encoder_error(error::encoder_error::type::CODEC_CTX_ALLOC_ERROR, "can not alloc encoder ctx");
        }

        // 配置编码参数
        if (config.is_copy())
        {
            // const int ret = avcodec_parameters_to_context(this->m_code_ctx, m_provider.get_codec());
            // if (ret < 0)
            // {
            //     throw error::encoder_error(error::encoder_error::type::CODEC_CTX_SET_ERROR, ret);
            // }
            // // m_code_ctx->time_base = this->m_provider.get_timebase();
            throw error::encoder_error(error::encoder_error::type::COPY_STREAM_FORBIDDEN,
                                       "copy stream is forbidden in encoder");
        }
        else
        {
            switch (config.get_profile())
            {
            case utils::video_config::profile::BASELINE:
                this->m_code_ctx->profile = FF_PROFILE_H264_BASELINE;
                break;
            case utils::video_config::profile::EXTENDED:
                this->m_code_ctx->profile = FF_PROFILE_H264_EXTENDED;
                break;
            case utils::video_config::profile::MAIN:
                this->m_code_ctx->profile = FF_PROFILE_H264_MAIN;
                break;
            case utils::video_config::profile::HIGH:
                this->m_code_ctx->profile = FF_PROFILE_H264_HIGH;
                break;
            }
            this->m_code_ctx->bit_rate = config.get_bitrate();
            this->m_code_ctx->rc_max_rate = config.get_max_bitrate();
            this->m_code_ctx->rc_min_rate = config.get_min_bitrate();
            m_code_ctx->width = config.get_width();
            m_code_ctx->height = config.get_height();
            m_code_ctx->gop_size = 10;
            m_code_ctx->time_base = this->m_provider.get_timebase();
            m_code_ctx->framerate = this->m_provider.get_codec()->framerate;
            m_code_ctx->pix_fmt = static_cast<AVPixelFormat>(this->m_provider.get_codec()->format);
        }

        //初始化上下文
        int ret = avcodec_open2(this->m_code_ctx, this->m_codec, nullptr);
        if (ret < 0)
        {
            throw error::encoder_error(error::encoder_error::type::CODEC_OPEN_ERROR, ret);
        }

        this->m_pkt = av_packet_alloc();
        if (!this->m_pkt)
        {
            throw error::encoder_error(error::encoder_error::type::PACKET_ALLOC_ERROR, "can not alloc packet");
        }
    }

    void general_encoder::init_audio_encoder(const utils::audio_config& config)
    {
        // 查找编码器
        this->m_codec = this->get_audio_encoder(config);
        if (!this->m_codec)
        {
            throw error::encoder_error(error::encoder_error::type::ENCODER_NOT_FOUND, "can not found encoder");
        }
        LOG(INFO) << "find audio encoder : " << this->m_codec->long_name;

        //分配上下文
        this->m_code_ctx = avcodec_alloc_context3(this->m_codec);
        if (!this->m_code_ctx)
        {
            throw error::encoder_error(error::encoder_error::type::CODEC_CTX_ALLOC_ERROR, "can not alloc encoder ctx");
        }

        // 配置编码参数
        if (config.is_copy())
        {
            // const int ret = avcodec_parameters_to_context(this->m_code_ctx, m_provider.get_codec());
            // if (ret < 0)
            // {
            //     throw error::encoder_error(error::encoder_error::type::CODEC_CTX_SET_ERROR, ret);
            // }
            throw error::encoder_error(error::encoder_error::type::COPY_STREAM_FORBIDDEN,
                                       "copy stream is forbidden in encoder");
        }
        else
        {
            this->m_code_ctx->bit_rate = config.get_bitrate();
            this->m_code_ctx->sample_rate = config.get_sample_rete();
            this->m_code_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
            int ret = av_channel_layout_from_mask(&this->m_code_ctx->ch_layout,AV_CH_LAYOUT_STEREO);
            if (ret < 0)
            {
                throw error::encoder_error(error::encoder_error::type::CHANNEL_LAYOUT_ERROR, ret);
            }
        }

        //初始化上下文
        int ret = avcodec_open2(this->m_code_ctx, this->m_codec, nullptr);
        if (ret < 0)
        {
            throw error::encoder_error(error::encoder_error::type::CODEC_OPEN_ERROR, ret);
        }

        this->m_pkt = av_packet_alloc();
        if (!this->m_pkt)
        {
            throw error::encoder_error(error::encoder_error::type::PACKET_ALLOC_ERROR, "can not alloc packet");
        }
    }

    void general_encoder::clean_encoder(error::encoder_error::type _type)
    {
        switch (_type)
        {
        case error::encoder_error::type::UNKNOWN_ERROR:
            av_packet_free(&this->m_pkt);
        case error::encoder_error::type::PACKET_ALLOC_ERROR:
        case error::encoder_error::type::CODEC_OPEN_ERROR:
        case error::encoder_error::type::CHANNEL_LAYOUT_ERROR:
        case error::encoder_error::type::CODEC_CTX_SET_ERROR:
            avcodec_free_context(&this->m_code_ctx);
        case error::encoder_error::type::CODEC_CTX_ALLOC_ERROR:
        case error::encoder_error::type::ENCODER_NOT_FOUND:
            ;
        }
        if (this->m_code_par)
        {
            avcodec_parameters_free(&this->m_code_par);
        }
    }

    void general_encoder::encode_packet()
    {
        while (!this->is_end && this->m_que.size() < 10)
        {
            auto frame = this->m_provider.get_frame();
            if (!frame)
            {
                this->is_end = true;
            }
            int ret = avcodec_send_frame(this->m_code_ctx, this->is_end ? nullptr : frame.get_frame());
            if (ret < 0)
            {
                throw error::encoder_error(error::encoder_error::type::SEND_FRAME_ERROR, ret);
            }
            while (ret >= 0)
            {
                ret = avcodec_receive_packet(this->m_code_ctx, this->m_pkt);
                if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
                {
                    break;
                }
                if (ret < 0)
                {
                    throw error::encoder_error(error::encoder_error::type::RECEIVE_PACKET_ERROR, ret);
                }
                this->m_que.emplace(this->m_pkt);
            }
        }
    }
}
